/*
 * CS340 fatboys
 */
package fatboy.addons;

import java.util.ArrayList;
import java.util.List;
import java.util.PriorityQueue;

import junit.framework.Assert;

import fatboy.communication.ObjectID;
import fatboy.communication.ResidentDatabase;
import fatboy.hypeerweb.Node;
import fatboy.hypeerweb.NodeAddress;

/**
 * The Visitor Broadcast.
 * @domain all postive Integers
 */
public class Broadcast implements Visitor {
	private static final long serialVersionUID = 9152971090282611536L;
	private int broadcastTotal;
	private ObjectID id;
	
	public Broadcast(){
		broadcastTotal = 0;
		setId(new ObjectID());
		ResidentDatabase.setObject(getId(), this);
	}
	
	/**
	 * Broadcast visit method.
	 * 
	 * @param thisNode the node being visited
	 * @param broadcastNode the broadcast origin
	 * 
	 * @pre broadcastNode is the origin of the broadcast
	 * @post this node has been visited and has propagated the broadcast
	 * 
	 * @see fatboy.addons.Visitor#visit(fatboy.hypeerweb.Node, java.lang.Object)
	 */
	public void visit(Node thisNode, Object broadcastNode) {	
		Assert.assertTrue(broadcastNode instanceof Node);
		Node broadcastOrigin = (Node) broadcastNode;
		incBroadcastTotal();			
		broadcast(thisNode, broadcastOrigin);
	}
	
	protected void incBroadcastTotal() {
		// TODO Auto-generated method stub
		broadcastTotal++;
	}

	/**
	 * Broadcasts to neighboring nodes.
	 * 
	 * @param thisNode this node
	 * @param broadcastOrigin the broadcast origin
	 * 
	 * @pre broadcastOrigin is the origin of the broadcast
	 * @post the broadcast has been propogated
	 */
	protected boolean broadcast(Node thisNode, Node broadcastOrigin) {
		boolean acknowledgement = true;
		List<Node> broadcastList = getBroadcastList(thisNode, broadcastOrigin.getAddress());
		for (Node toBroadcast : broadcastList) {
//			System.out.println(thisNode.getAddress()+" sending broadcast to "
//					+ toBroadcast.getAddress());		
			acknowledgement &= toBroadcast.accept(this, broadcastOrigin);
		}
		return acknowledgement;
	}
	
	/**
	 * Gets a list of neighbors which this node should broadcast to.
	 * 
	 * @param thisNode the this node
	 * @param broadcastAddress the broadcast address
	 * 
	 * @pre thisNode is a node in a valid hyperweb, 
	 * broadcastAddress is the address of the broadcasting node
	 * @post a list of neighbors that the node should broadcast to is returned
	 */
	protected List<Node> getBroadcastList(Node thisNode, NodeAddress broadcastAddress) {		
		List<Node> broadcastNeighbors = new ArrayList<Node>();
		
		NodeAddress thisAddress = thisNode.getAddress();		
		NodeAddress thisAddressXORd = thisAddress.XOR(broadcastAddress);
		/*
		Node tempXORnode = thisNode.getNodeAt(thisAddressXORd);
		PriorityQueue<Node> XORdNeighbors = new PriorityQueue();
		if(tempXORnode!=null){
			XORdNeighbors= tempXORnode.getNeighbors();		
		}
		*/
		PriorityQueue<Node> XORdNeighbors = thisNode.getNeighbors();
		
		for (Node XORdNeighbor : XORdNeighbors) {
			NodeAddress XORdNeighborAddress = XORdNeighbor.getAddress().XOR(broadcastAddress);
			if (thisAddressXORd.isLessSignificantNeighbor(XORdNeighborAddress)) {				

//				if(XORdNeighbor != null){
				broadcastNeighbors.add(XORdNeighbor);
//				}
				/*else{
					List<Node> ptrXORList = thisNode.getNodeAt(thisAddress).getPointers().getNodeList();
					for(Node ptr : ptrXORList){
						broadcastNeighbors.add(ptr);
					}
				}*/
			}
		}
		//Test all Up Pointers
		List<Node> ptrXORList = thisNode.getNodeAt(thisAddress).getPointers().getNodeList();
		for(Node ptr : ptrXORList){
			NodeAddress XORdptrAddress = ptr.getAddress().XOR(broadcastAddress);
			if (thisAddressXORd.isLessSignificantNeighbor(XORdptrAddress)) {				
				broadcastNeighbors.add(ptr);
			}			
		}
		return broadcastNeighbors;
	}
	
	/**
	 * Used for getting total nodes visited
	 * @return total
	 */
	public int getBroadcastTotal() {
		return broadcastTotal;
	}

	/** Definitions for whitebox testing */
	private static int THREE = 3;
	private static int FOUR = 4;
	/**
	 * Unit Tests for whitebox testing
	 * 
	 * The main focus of this method is doing branch coverage
	 * of getBroadcastList and loop testing of 0,1, <1 tests
	 * The loop in question is all the neighbors of a current node to 
	 * also broadcast to. I do webs of various sizes (8,4,2,1) to create 
	 * the cases where there are 0, 1, <1 neighbors.
	 * 
	 * This also works as internal boundry testing as the nodes and
	 * their neighbors are preset as passed in.
	 */
	public boolean unitTests(List<Node> web){
		boolean returnRes = true;

		Broadcast bcTester = new Broadcast();
		//Loop Testing
		//Test loop testing and code coverage testing
		//loop that runs >2 times
		List<Node> neighbors = getBroadcastList(web.get(0),web.get(0).getAddress());
		returnRes &= (neighbors.size()== THREE);
		
		int i = web.size()-1;

		neighbors = getBroadcastList(web.get(i),web.get(0).getAddress());
		returnRes &= (neighbors.size()==0);
		
		//updates web size to 4
		while(web.size()>FOUR){
			web.get(0).remove(web.get(i));
			web.remove(i--);
		}
		
		//Test Loop Twice
		neighbors = getBroadcastList(web.get(0),web.get(0).getAddress());
		returnRes &= (neighbors.size()== 2);	//Loop that runs twice
		
		while(web.size()>2){
			web.get(0).remove(web.get(i));
			web.remove(i--);
		}
		
		//Test loop Once
		neighbors = getBroadcastList(web.get(0),web.get(0).getAddress());
		returnRes &= (neighbors.size()== 1);	//Test loop that enters once
		
		web.get(0).remove(web.get(i));
		web.remove(i--);
		
		//Never enter loop
		neighbors = getBroadcastList(web.get(0),web.get(0).getAddress());
		returnRes &= (neighbors.size()== 0);	//Test loop that never starts
		return returnRes;
	}
	public Object writeReplace(){
		return new ProxyBroadcast(this.getId());
	}

	public void setId(ObjectID id) {
		this.id = id;
	}

	public ObjectID getId() {
		return id;
	}



}
